/**
 * Copyright 2006 gworks.com.au
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. (http://www.apache.org/licenses/LICENSE-2.0)
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for 
 * the specific language governing permissions and limitations under the License.
 *
 * <dhamma-message src="Atisha (11th century Tibetan Buddhist master)">
 * 	The greatest achievement is selflessness. The greatest worth is self-mastery.
 * 	The greatest quality is seeking to serve others. The greatest precept is continual awareness.
 * 	The greatest medicine is the emptiness of everything. The greatest action is not conforming with the worlds ways.
 * 	The greatest magic is transmuting the passions. The greatest generosity is non-attachment.
 * 	The greatest goodness is a peaceful mind. The greatest patience is humility.
 * 	The greatest effort is not concerned with results. The greatest meditation is a mind that lets go.
 * 	The greatest wisdom is seeing through appearances. 
 * </dhamma-message>
 */
package org.javaongems.client.stdctrls;

import java.util.ArrayList;
import java.util.HashMap;

import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.EventPreview;
import com.google.gwt.user.client.ui.Widget;

/**
 * Provides the ability to monitor drag and drop events.
 * <p>
 * <ul>
 * 	<li> register listening to event preview facility on start()
 * 	<li> deregister from the event preview favility on end()
 * 	<li> register source widgets for dragging
 * 	<li> register target widgets for dropping
 * 	<li> only follow drag events for source widgets
 * 	<li> notify drag and drop listener when mouse is dragged over a target 
 * 	<li> notify drag and drop listener when mouse is released and dropped on a target 
 * </ul>
 * </p>
 * @author Ashin Wimalajeewa (ash)
 */
public class DragAndDropMonitor implements EventPreview {
	private ArrayList dropTargets = new ArrayList();
	private ArrayList dragSources = new ArrayList();
	private boolean dragging;
	private int[] firstXY = new int[2];
	private Event firstEvent, lastEvent;
	private int lastEventType;
	private Element lastToElement;
	private HashMap elements = new HashMap();
	private Widget sourceWidget, targetWidget;
	private DragAndDropListener dragAndDropListener;

	public DragAndDropMonitor() {
	}
	
	public DragAndDropMonitor(DragAndDropListener lsnr) {
		setDragAndDropListener(lsnr);
	}
	
	public void start() {
		DOM.addEventPreview(this);
	}

	public void stop() {
		DOM.removeEventPreview(this);
	}
	
	public void setDragAndDropListener(DragAndDropListener lsnr) {
		dragAndDropListener = lsnr;
	}
	
	public void clearDragAndDropListener() {
		dragAndDropListener = null;
	}
	
	public Event getLastEvent() {
		return lastEvent;
	}
	
	public int getLastEventType() {
		return lastEventType;
	}
	
	public Widget getSourceWidget() {
		return sourceWidget;
	}
	
	public Widget getLastEventTargetWidget() {
		return targetWidget;
	}
	
	public Event getFirstEvent() {
		return firstEvent;
	}
	
	public int[] getFirstXyPositions() {
		return firstXY;
	}

	public void addDropTarget(Widget target) {
		if (!dropTargets.contains(target)) {
			dropTargets.add(target);
			elements.put("" + target.getElement().hashCode(), target);
		}
	}

	public void removeDropTarget(Widget target) {
		dropTargets.remove(target);
		elements.remove("" + target.getElement().hashCode());
	}

	public void addDragSource(Widget source) {
		if (!dragSources.contains(source)) {
			dragSources.add(source);
			elements.put("" + source.getElement().hashCode(), source);
		}
	}

	public void removeDragSource(Widget source) {
		dragSources.remove(source);
		elements.remove("" + source.getElement().hashCode());
	}

	public boolean onEventPreview(Event event) {
		lastEventType = DOM.eventGetType(event);
		lastEvent = event;
		lastToElement = DOM.eventGetToElement(event);
		switch (lastEventType) {
		case Event.ONCLICK:
		case Event.ONBLUR:
		case Event.ONMOUSEUP: {
			onMouseUp();
			break;
		}
		case Event.ONMOUSEDOWN: {
			onMouseDown();
			break;
		}
		case Event.ONMOUSEMOVE:
		case Event.ONMOUSEOUT:
		case Event.ONMOUSEOVER: {
			onMouseDragState();
			break;
		}
		default: {
			if (!(lastEventType == 32 || lastEventType == 64 || lastEventType == 32768)) {
				System.out.println("lastEventType=" + lastEventType);
			}
		}
		}
		return true;
	}

	protected void onMouseDown() {
		dragging = false;
		Element elm = DOM.eventGetTarget(lastEvent);
		Widget widget = getTargetElement(elm);
		if (widget != null) {
			sourceWidget = widget;
			firstXY[0] = DOM.eventGetClientX(lastEvent) - DOM.getAbsoluteLeft(elm);
			firstXY[1] = DOM.eventGetClientY(lastEvent) - DOM.getAbsoluteLeft(elm);
			dragging = true;
		}
	}

	protected void onMouseUp() {
		System.out.println("mouse is up");
		if ((lastEventType == Event.ONCLICK || lastEventType == Event.ONMOUSEUP) && dragging) {
			int x = DOM.eventGetClientX(lastEvent) - DOM.getAbsoluteLeft(lastToElement);
			int y = DOM.eventGetClientY(lastEvent) - DOM.getAbsoluteTop(lastToElement);
			if (targetWidget != null && dragAndDropListener != null)
				dragAndDropListener.onDragDrop(targetWidget, sourceWidget, x, y, lastEvent);
		}
		dragging = false;
		sourceWidget = null;
		targetWidget = null;
	}

	protected void onMouseDragState() {
		if (dragging) {
			int x = DOM.eventGetClientX(lastEvent) - DOM.getAbsoluteLeft(lastToElement);
			int y = DOM.eventGetClientY(lastEvent) - DOM.getAbsoluteTop(lastToElement);
			boolean continueWithDrop = false;
			if (lastToElement != null) {
				targetWidget = getTargetElement(lastToElement);
				if (dragAndDropListener != null)
					continueWithDrop = dragAndDropListener.onDragOver(targetWidget, sourceWidget, x, y, lastEvent);
			} 
			if (!continueWithDrop)
				targetWidget = null;
		}
	}

	protected Widget getTargetElement(Element elm) {
		int hash = elm.hashCode();
		Widget ret = (Widget) elements.get("" + hash);
		return ret;
	}
}
